Skip to content

OODA: lineage and a sidepeek for every stage (PR 3 of 5) - #201

Merged
hhff merged 16 commits into
mainfrom
ooda-lineage
Nov 13, 2026
Merged

hhff merged 16 commits into
mainfrom
ooda-lineage

Conversation

@hhff

@hhff hhff commented Nov 11, 2026

Copy link
Copy Markdown
Member

PR 3 of 5 for the inspectable OODA loop (spec and plan in #193). Stacked on #200 (base ooda-signals); it retargets as those merge.

What changes

  • Lineage, computed by the kit. linkIndex() builds one index over the folded log, keyed by field: evidence, refs, blocks, knowledge, approved_by, decision, plus a signal's members and a correction. Every edge points from the record cited to the record citing it, which runs earlier to later. An add-ticket act points to the ticket it made. lineage(id) walks up 6 hops and down 4, to at most 120 nodes. Work-graph and conversation nodes (tickets, goals, threads) are drawn but never expanded. More than 5 siblings of one kind on one neighbour, in one direction, collapse into a group node. peek <kind> <id> --json carries lineage and lineage_mermaid; peek <kind> <id> --mermaid prints the graph as text for retros and PR bodies.
  • A sidepeek for every stage. Observations, recurring signals (#signal/<key>; keys may hold /) and learning records get their own panels. Lessons, problems, decisions and acts gain the lineage block. Clicking anywhere on a Loop card opens its panel, through one delegated [data-open] handler. That handler ignores Shift (thread aiming), modifier keys, controls inside the card and text selections, and replaces the incident cards' own onclick. o- and q- ids in text become links.
  • Mermaid, clickable, with a list fallback. The page's existing Mermaid loader is factored into drawMermaid(src). After render, each node is wrapped in a link to its panel (matched by data-id or by its flowchart-n<i>-… id). The wrapped SVG is cached by node ids, so refreshes don't redraw it. When Mermaid can't load, the panel shows the same graph as a list grouped by stage, and never the raw Mermaid source. Labels are plain text cut to 60 characters, with no ", ;, # or %.
  • Where an observation sits. The kit computes this once (placeOf, on top of explained() and covered()) and sends it on cards and panels. The states are: not oriented yet, part of a recurring signal (n of 3), explained, ruled noise, ignored at triage, set aside until a time, or carried on to a later stage.
  • Orientation debt. ooda.debt.signals holds recurring signals unexplained for more than a day, oldest first. The Orient tab shows them above Recurring signals.
  • The operator can read lineage. peek is a pure read, so it joins OPERATOR_READS. The operator snapshot test covers peek of a lesson (--json and --mermaid), a problem, a goal and a sensor, and confirms files and git status are unchanged byte for byte.
  • Docs: supervisor.md §3 (lineage and orientation debt).

For the operator's review (#190 follow-up)

The review can read:

  • peek <kind> <id> --json → lineage, and --mermaid
  • dashboard-data → ooda.debt and ooda.observations[*].place

Tests

  • test/ooda-lineage.test.mjs, 7 tests:
    • the incident chain walks both ways from every node
    • noise-ruling edges
    • collapse and the 120-node cap
    • label sanitising
    • the --mermaid shape
    • the place states
    • orientation debt
    • Two of the tests run the page's own place and fallback code in vm, so CI covers them without Chrome.
  • test/dashboard.test.mjs adds a Chrome test: a card click opens its panel, and a Mermaid node click opens the right one.
  • The G1 golden is recorded again. Only keys were added.
  • Full suite on Node 24: 411 of 412 passed, with 1 existing skip. CI covers Node 18 to 24.

One-way doors

None. No log fields change.

🤖 Generated with Claude Code

hhff and others added 15 commits November 11, 2026 13:08
A lesson, an incident or a decision should show what it came from and what
came of it. One link index, built once per read of the log from the fields
that cite (evidence, refs, blocks, knowledge, approved_by, decision), gives
lineage(id): 6 steps up, 4 down, at most 120 nodes, more than five siblings
of one kind on one neighbour as one node. The work graph is drawn but never
followed. peek carries it (lineage, lineage_mermaid) and --mermaid prints it
for retros and PR bodies; labels are stripped so no text reaches Mermaid's
syntax. signalGroups() keeps every group, so a card can say how near a
signal an observation is; orientClusters() is built on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p card opens

An observation, a recurring signal and the learning record had no sidepeek,
and a Loop card opened only from its timestamp. Now each opens its own: an
observation's says where it came from first (its computed provenance, in
plain words) and where it sits in the loop (not oriented yet, n of the 3 a
signal takes, explained, ruled noise, ignored, set aside, or carried on to
the furthest step), which the kit computes once (placeOf) for the card and
the peek alike. A signal opens by its key, / and all, with its observations,
how long it has waited and what would explain it. One delegated handler
opens any [data-open] card, leaving a link, a control, a modified click, a
pinned thread layer and a selection alone; the incidents' own handlers go,
so nothing fires twice. o- and q- ids in text link to their sidepeeks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/devteam/scripts/devteam.mjs
…ent's fingerprint

ooda-signals made explained() the one definition of what explains an
observation, keys included: a noise ruling that names a signal's key covers
its later observations, and an open incident holds its fingerprints. A card
built on a second definition would say "not oriented yet" for a spike the
incident already holds; now it says the incident explains it, or that it
was ruled noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each OODA sidepeek (observation, signal, lesson, problem, decision, act,
learning) now shows how the record came about and what came of it.
renderMermaid's initialize, render and sanitize are factored into
drawMermaid(text) and shared. The list, grouped by stage with every record
a link, is the first paint and what stays when Mermaid can't load; the graph,
once drawn, has each node wrapped in a link to its sidepeek and is cached by
its nodes' ids, so a refresh that changes none of them leaves the drawer as
it is. Mermaid 11.4.1's sanitized SVG carries no data-id on node groups, so
a node is found by data-id or by its flowchart-n<i>-<counter> id. The raw
Mermaid source is never shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A recurring signal can sit unexplained for days with nothing saying so.
dashboard-data's ooda.debt.signals lists the ones unexplained for more than
a day, oldest first, read off the same explained() Orient uses, and the
Orient tab shows them above the recurring signals, each card opening its
signal. Resolved incidents nobody learned from join it in PR 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The box's operator reviews how each repo's loop runs, and the lineage is
in peek (--json's lineage, --mermaid). peek only reads: the operator's
read-only test now runs peek for a lesson (--json and --mermaid), a
problem, a goal and a sensor, and the files and git status are what they
were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
supervisor.md §3 step 4 says what orientation debt is and where it shows,
and how to read a record's lineage (peek --json) or paste it into a retro or
a PR body (peek --mermaid).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's golden was recorded before the OODA PRs. dashboard-data's
observations now carry stage and provenance (PR 2) and place (PR 3), ooda
carries debt, and peek ticket says stage null. Each is a key added beside
what was there; no value a single-repo project printed before is changed or
gone, so the golden is recorded again rather than the views narrowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ooda-signals' explained() now maps a signal key to until when it is
explained, read with covered(): a noise ruling naming a key covers that
signal's observations up to the ruling. Where an observation sits reads it
the same way (one after a ruling is not oriented yet, not ruled noise), and
orientation debt follows through orientClusters. supervisor.md keeps both
sides' sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- linkIndex: an incident holds each observation on its fingerprints from its
  opening to its resolution ('held'), so a later spike's sidepeek has the
  incident in its lineage and its place follows it on
- one edge a pair: a second field citing it joins its why ("evidence, asks about")
- inbox ("Asked you") and measurement ("Measured") stages drawn in their own
  groups, with a stroke each, in the CLI's Mermaid and the page's list
- peek signal <unknown> --mermaid dies as --json does
- the page: Orientation debt and Recurring signals cards have a link inside;
  the lineage graph is kept by node id, stage and label, and a failed render
  is tried again on the next open; a signal's "Recurring" is blank while loading
- tests: held edges, the hop bound, joined whys, group edges to two neighbours
- spec §4: the ad-hoc indexes stay as they are for now

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…earch and peek

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…graph cache

Review fixes from the upstream loop: an approved_by edge reads approved only
when the approval checks out (a decision with no authority gap, an act
carrying one out, an act resting on the owner's accepted rule or an answered
question); anything else cites it, so a supervisor's lesson never reads as
the owner approving. The help names --mermaid and the new kinds, and the
page keeps the last 50 drawn graphs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hhff
hhff changed the base branch from ooda-signals to main November 13, 2026 18:32
@hhff

hhff commented Nov 13, 2026

Copy link
Copy Markdown
Member Author

Fable reviewed it and found no injection or authority hole. Mermaid labels are sanitised, strict mode and DOMPurify are on, and the routes are safe. The lineage's "approved" edge is drawn only when the kit's authority checks hold, and "cites" otherwise. Retargeted to main after #200 merged. CI 9/9 at 73190dd.

@hhff
hhff merged commit e2610ed into main Nov 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant